package com.xqboss.apps.service.market;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xqboss.apps.constant.Config;
import com.xqboss.apps.constant.LockKey;
import com.xqboss.apps.constant.RedisKey;
import com.xqboss.apps.domain.box.Box;
import com.xqboss.apps.domain.equipment.Equipment;
import com.xqboss.apps.domain.equipment.EquipmentLevelPrice;
import com.xqboss.apps.domain.market.Market;
import com.xqboss.apps.domain.market.MarketMain;
import com.xqboss.apps.domain.member.SysUserNft;
import com.xqboss.apps.domain.nft.Nft;
import com.xqboss.apps.domain.nft.NftNumber;
import com.xqboss.apps.domain.user.SysUserWallet;
import com.xqboss.apps.domain.user.UserAssetLidou;
import com.xqboss.apps.domain.user.UserAssetWrench;
import com.xqboss.apps.domain.user.UserBox;
import com.xqboss.apps.dto.market.*;
import com.xqboss.apps.enums.market.MarketStatusEnum;
import com.xqboss.apps.enums.market.MarketTypeEnum;
import com.xqboss.apps.enums.nft.NftTypeEnum;
import com.xqboss.apps.enums.nft.ResellEnum;
import com.xqboss.apps.enums.nft.UserNftStatusEnum;
import com.xqboss.apps.enums.order.OrderStatusEnum;
import com.xqboss.apps.enums.user.UserWalletEnum;
import com.xqboss.apps.enums.user.WalletChangeDirectionEnum;
import com.xqboss.apps.enums.user.WalletChangeTypeEnum;
import com.xqboss.apps.enums.user.WalletWrenchChangeTypeEnum;
import com.xqboss.apps.mapper.market.MarketMapper;
import com.xqboss.apps.mapper.order.OrderMainMapper;
import com.xqboss.apps.service.box.BoxService;
import com.xqboss.apps.service.equipment.EquipmentLevelPriceService;
import com.xqboss.apps.service.equipment.EquipmentService;
import com.xqboss.apps.service.nft.NftNumberService;
import com.xqboss.apps.service.nft.NftService;
import com.xqboss.apps.service.punch.UserPunchService;
import com.xqboss.apps.service.user.*;
import com.xqboss.apps.vo.equipment.EquipmentInfoVo;
import com.xqboss.apps.vo.market.*;
import com.xqboss.common.constant.CacheConstants;
import com.xqboss.common.core.domain.R;
import com.xqboss.common.core.redis.RedisCache;
import com.xqboss.common.core.redis.lock.LockValue;
import com.xqboss.common.core.redis.lock.RedisLock;
import com.xqboss.common.exception.ServiceException;
import com.xqboss.common.utils.StringUtils;
import com.xqboss.system.service.ISysConfigService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 交易市场接口
 *
 * @author wangcong
 * @date 2023-11-02
 */
@Slf4j
@Service
public class MarketService extends ServiceImpl<MarketMapper, Market> {
    @Autowired
    private BoxService boxService;

    @Autowired
    private UserBoxService userBoxService;

    @Autowired
    private UserBoxNftService userBoxNftService;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private NftService nftService;

    @Autowired
    private NftNumberService nftNumberService;

    @Lazy
    @Autowired
    private SysUserNftService userNftService;

    @Lazy
    @Autowired
    private EquipmentService equipmentService;

    @Autowired
    private ISysConfigService sysConfigService;

    @Autowired
    private OrderMainMapper orderMainMapper;

    @Lazy
    @Autowired
    private SysUserWalletService userWalletService;

    @Lazy
    @Autowired
    private MarketMainService marketMainService;

    @Lazy
    @Autowired
    private EquipmentLevelPriceService equipmentLevelPriceService;
    @Autowired
    private UserAssetWrenchService userAssetWrenchService;
    @Autowired
    private UserAssetLidouService userAssetLidouService;
    @Autowired
    private UserAssetWrenchRecordService userAssetWrenchRecordService;
    @Autowired
    private UserPunchService userPunchService;


    /**
     * 市场典藏、装备列表
     *
     * @param dto
     * @param maxDepth 递归深度，建议：3
     * @return
     */
    public List<MarketListVo> marketList(MarketDto dto, int maxDepth) {
        //线程等待递归完（递归3次，每次等待200ms，共600ms，600ms后未获取到数据则提示用户）还没获取到数据，返回空
        if (maxDepth <= 0) {
            log.info("[市场列表查询接口]线程等待递归完还没获取到数据，提示：访问人数过多，请稍后再试！ ");
            throw new ServiceException("访问人数过多，请稍后再试！");
        }
        //查询缓存
        String redisKey = CacheConstants.MARKET_LIST_KEY + dto.getType() + dto.getPageNum();
        String redisLock = RedisKey.KEY_MARKET_SETIFPRESENT;
        if (dto.getCategoryNftId()!=null){
            redisKey = redisKey + dto.getCategoryNftId();
            redisLock = redisLock +":"+dto.getCategoryNftId();
        }
        if (dto.getSaleStatus()!=null){
            redisKey = redisKey + dto.getSaleStatus();
            redisLock = redisLock +":"+dto.getSaleStatus();
        }
        if (dto.getLossDegree()!=null){
            redisKey = redisKey + dto.getLossDegree();
            redisLock = redisLock +":"+dto.getLossDegree();
        }
        if (redisCache.hasKey(redisKey)) {
            List<MarketListVo> listCache = redisCache.getCacheObject(redisKey);
            if (CollUtil.isNotEmpty(listCache)) {
                //为了解决前端数据重复问题 单独处理缓存type
                listCache.forEach(item -> item.setType(dto.getType()));
                return listCache;
            }
            return listCache;
        }
        //查询数据库（锁用户）
        List<MarketListVo> list = null;
        Boolean ifPresent = redisCache.setIfAbsent(redisLock, "1", 3);
        try{
            if (ifPresent) {
                dto.startPage();
                switch (dto.getType()) {
                    case EQUIPMENT:
                    case NORMAL:
                        list = marketMainService.getMarketList(dto);
                        redisCache.setCacheObject(redisKey, CollUtil.isEmpty(list)?new ArrayList<>():list, 5, TimeUnit.SECONDS);
                        break;

                    case BOX:
                        list = boxService.getBoxList(dto);
                        redisCache.setCacheObject(redisKey, CollUtil.isEmpty(list)?new ArrayList<>():list, 5, TimeUnit.SECONDS);
                        break;

                    default:
                        throw new ServiceException("市场类型错误");
                }
                //为了解决前端数据重复问题 单独处理type
                list.forEach(item -> item.setType(dto.getType()));
            }else{
                //线程等待 3s
                try {
                    Thread.sleep(200);

                    return marketList(dto,maxDepth-1);
                } catch (InterruptedException e) {
                    log.error("[市场列表查询接口]线程等待异常",e);
                }
            }
        }finally {
            //删除锁
            if (CollUtil.isNotEmpty(list)) {
                redisCache.deleteObject(redisLock);
                log.info("[市场列表查询接口]删除锁ok");
            }
        }
        return list;
    }

    public IPage<MarketListVo> marketListPlus(MarketDto<MarketListVo> dto, int maxDepth) {
        //线程等待递归完（递归3次，每次等待200ms，共600ms，600ms后未获取到数据则提示用户）还没获取到数据，返回空
        if (maxDepth <= 0) {
            log.info("[市场列表查询接口]线程等待递归完还没获取到数据，提示：访问人数过多，请稍后再试！ ");
            throw new ServiceException("访问人数过多，请稍后再试！");
        }
        //查询缓存
        String redisKey = CacheConstants.MARKET_LIST_KEY + dto.getType() + dto.getPageNum();
        String redisLock = RedisKey.KEY_MARKET_SETIFPRESENT;
        if (dto.getCategoryNftId()!=null){
            redisKey = redisKey + dto.getCategoryNftId();
            redisLock = redisLock +":"+dto.getCategoryNftId();
        }
        if (dto.getSaleStatus()!=null){
            redisKey = redisKey + dto.getSaleStatus();
            redisLock = redisLock +":"+dto.getSaleStatus();
        }
        if (dto.getLossDegree()!=null){
            redisKey = redisKey + dto.getLossDegree();
            redisLock = redisLock +":"+dto.getLossDegree();
        }
        if (redisCache.hasKey(redisKey)) {
            IPage<MarketListVo> ipage = redisCache.getCacheObject(redisKey);
            if (ObjUtil.isNotEmpty(ipage) && CollUtil.isNotEmpty(ipage.getRecords())) {
                //为了解决前端数据重复问题 单独处理缓存type
                ipage.getRecords().forEach(item -> item.setType(dto.getType()));
                return ipage;
            }
            return ipage;
        }
        //查询数据库（锁用户）
        IPage<MarketListVo> iPage = null;
        Boolean ifPresent = redisCache.setIfAbsent(redisLock, "1", 3);
            if (ifPresent) {
                try{
                    switch (dto.getType()) {
                        case EQUIPMENT:
                        case NORMAL:
                            iPage = marketMainService.getMarketListPlus(dto);
                            redisCache.setCacheObject(redisKey, ObjUtil.isNotEmpty(iPage)?iPage:new Page<MarketListVo>(), 5, TimeUnit.SECONDS);
                            break;

                        case BOX:
                            iPage = boxService.getBoxListPlus(dto);
                            redisCache.setCacheObject(redisKey, ObjUtil.isNotEmpty(iPage)?iPage:new Page<MarketListVo>(), 5, TimeUnit.SECONDS);
                            break;

                        default:
                            throw new ServiceException("市场类型错误");
                    }
                    //为了解决前端数据重复问题 单独处理type
                    iPage.getRecords().forEach(item -> item.setType(dto.getType()));
                }finally {
                    //删除锁
                    redisCache.deleteObject(redisLock);
                    log.info("[市场列表查询接口]从数据库查询到数据，删除锁ok");
                }
            }else{
                //线程等待 3s
                try {
                    Thread.sleep(200);

                    return marketListPlus(dto,maxDepth-1);
                } catch (InterruptedException e) {
                    log.error("[市场列表查询接口]线程等待异常",e);
                }
            }
        return iPage;
    }
    /**
     * 市场扩展列表
     *
     * @param params
     * @return
     */
    public R marketExtenList(MarketExtendDto params) {
        if (StringUtils.isEmpty(params.getOrderByColumn())) {
            params.setOrderByColumn("createTime");
            params.setSortAsc(false);
        }
        switch (params.getType()) {
            case NORMAL:
            {
                MarketMain marketMain = marketMainService.getById(params.getId());
                if(marketMain == null || !Objects.equals(marketMain.getType(), params.getType())){
                    throw new ServiceException("藏品已下架!");
                }
                Long nftId = marketMain.getSourceId();
                LambdaQueryWrapper<Market> wp = new LambdaQueryWrapper<>();
                wp.eq(Market::getType, params.getType());
                wp.eq(Market::getNftId, nftId);
                wp.eq(Market::getStatus, MarketStatusEnum.TRADING);
                if (Objects.nonNull(params.getStatus()) && params.getStatus() == 0) {
                    wp.eq(Market::getStock, 0);
                } else if (Objects.nonNull(params.getStatus()) && params.getStatus() == 1) {
                    wp.gt(Market::getStock, 0);
                }
                params.startPageAndOrder();
                List<Market> normalList = list(wp);
                List<MarketNormalExtendVo> normalExtendVos = BeanUtil.copyToList(normalList, MarketNormalExtendVo.class);
                return R.ok(normalExtendVos, normalList);
            }
            case EQUIPMENT:
            {
                MarketMain marketMain = marketMainService.getById(params.getId());
                if(marketMain == null || !Objects.equals(marketMain.getType(), params.getType())){
                    throw new ServiceException("装备已下架!");
                }
                Long nftId = marketMain.getSourceId();
                params.setId(nftId);
                params.setIsNew(marketMain.getIsNew());
                params.startPageAndOrder();
                List<MarketEquipmentExtendVo> list = baseMapper.getEquipmentList(params);
                return R.ok(list);
            }
            case BOX:
                //查询缓存
//                List<MarketBoxExtendVo> listCacheBox = redisCache.getCacheList(CacheConstants.MARKET_EXTEND_LIST_KEY + params.getType() + params.getPageNum());
//                if (CollUtil.isNotEmpty(listCacheBox)) {
//                    return R.ok(listCacheBox);
//                }
                params.startPageAndOrder();
                List<Market> boxList = list(new LambdaQueryWrapper<Market>()
                        .eq(Market::getType, params.getType())
                        .gt(Market::getStock, 0)
                        .eq(Market::getStatus, MarketStatusEnum.TRADING)
                        .eq(Market::getBoxId, params.getId()));
                List<MarketBoxExtendVo> boxExtendVos = BeanUtil.copyToList(boxList, MarketBoxExtendVo.class);
                //设置缓存
//                if (CollUtil.isNotEmpty(boxExtendVos)) {
//                    redisCache.setCacheList(CacheConstants.MARKET_EXTEND_LIST_KEY + params.getType() + params.getPageNum(), boxExtendVos, 1, TimeUnit.SECONDS);
//                }
                return R.ok(boxExtendVos, boxList);
            case MATERIAL:
                //查询缓存
//                List<MarketNormalExtendVo> listCacheMaterial = redisCache.getCacheList(CacheConstants.MARKET_EXTEND_LIST_KEY + params.getType() + params.getPageNum());
//                if (CollUtil.isNotEmpty(listCacheMaterial)) {
//                    return R.ok(listCacheMaterial);
//                }
                params.startPageAndOrder();
                List<Market> marketList = list(new LambdaQueryWrapper<Market>()
                        .eq(Market::getType, params.getType())
                        .gt(Market::getStock, 0)
                        .eq(Market::getStatus, MarketStatusEnum.TRADING)
                        .eq(Market::getSourceId, params.getId()));
                List<MarketNormalExtendVo> materialVo = BeanUtil.copyToList(marketList, MarketNormalExtendVo.class);
                //设置缓存
//                if (CollUtil.isNotEmpty(materialVo)) {
//                    redisCache.setCacheList(CacheConstants.MARKET_EXTEND_LIST_KEY + params.getType() + params.getPageNum(), materialVo, 1, TimeUnit.SECONDS);
//                }
                return R.ok(materialVo, marketList);
            default:
                throw new ServiceException("市场类型错误");
        }
    }


    /**
     * 获取市场信息
     *
     * @return
     */
    public Market getMarketInfo(Long id) {
        Market market = getById(id);
        return market;
    }

    /**
     * 锁定库存
     *
     * @param marketId
     * @param quantity
     */
    @RedisLock(lockKey = LockKey.LOCK_MARKET)
    @Transactional(rollbackFor = Exception.class)
    public void lockStock(@LockValue Long marketId, Integer quantity) {
        boolean update = update(new LambdaUpdateWrapper<Market>()
                .setSql("stock = stock - " + quantity)
                .setSql("lock_stock = lock_stock + " + quantity)
                .ge(Market::getStock, quantity)
                .eq(Market::getId, marketId));
        if (!update) {
            throw new ServiceException("库存不足！");
        }
    }

    /**
     * 解锁库存
     *
     * @param marketId 市场ID
     * @param quantity
     */
    @RedisLock(lockKey = LockKey.LOCK_MARKET)
    @Transactional(rollbackFor = Exception.class)
    public void unlockStock(@LockValue Long marketId, Integer quantity) {
        Market market = getById(marketId);
        if (market == null) {
            log.error("解锁库存失败，请检查原因，商品：{}  数量：{}", marketId, quantity, new Exception());
            throw new ServiceException("商品不存在！");
        }
        if (market.getStatus() == MarketStatusEnum.CANCEL) {
            // 如果商家已经取消出售，则回退到库存
            boolean update = update(new LambdaUpdateWrapper<Market>()
                    .setSql("lock_stock = lock_stock - " + quantity)
                    .ge(Market::getLockStock, quantity)
                    .eq(Market::getId, marketId));
            if (!update) {
                log.error("解锁库存失败，请检查原因，商品：{}  数量：{}", marketId, quantity, new Exception());
                throw new ServiceException("库存不足！");
            }
            if (market.getType() == MarketTypeEnum.BOX) {
                userBoxService.rollbackStock(market.getUserId(), market.getSourceId(), quantity);
            } else if(market.getType() == MarketTypeEnum.MATERIAL) {
                if(Objects.equals(market.getSourceId(), UserWalletEnum.INTEGRAL.getValue().longValue())){
                    // 取消热豆
                    userWalletService.increase(market.getUserId(), UserWalletEnum.INTEGRAL, new BigDecimal(String.valueOf(quantity)), WalletChangeTypeEnum.CANCEL_MARKET, market.getSourceId(), WalletChangeTypeEnum.CANCEL_MARKET.getMsg());
                } else if (Objects.equals(market.getSourceId(), UserWalletEnum.WRENCH.getValue().longValue())) {
                    //取消扳手
                    userAssetWrenchRecordService.updateWrenchBalance(market.getUserId(), WalletWrenchChangeTypeEnum.CANCEL_MARKET, WalletChangeDirectionEnum.IN, new BigDecimal(String.valueOf(quantity)), market.getSourceId(), WalletWrenchChangeTypeEnum.CANCEL_MARKET.getMsg());
                } else{
                    log.error("解锁库存出现错误,未处理的市场类型，寄售ID：{}  数量：{}", marketId, quantity, new Exception());
                }
            }else{
                // 暂时其他类型不可能走到这里呢，记录问题
                log.error("解锁库存出现错误,未处理的市场类型，寄售ID：{}  数量：{}", marketId, quantity, new Exception());
            }
        } else {
            boolean update = update(new LambdaUpdateWrapper<Market>()
                    .setSql("stock = stock + " + quantity)
                    .setSql("lock_stock = lock_stock - " + quantity)
                    .ge(Market::getLockStock, quantity)
                    .eq(Market::getId, marketId));
            if (!update) {
                log.error("解锁库存失败，请检查原因，商品：{}  数量：{}", marketId, quantity, new Exception());
                throw new ServiceException("库存不足！");
            }
        }

    }

    /**
     * 扣除锁定库存
     *
     * @param marketId 市场ID
     * @param quantity
     */
    @RedisLock(lockKey = LockKey.LOCK_MARKET)
    @Transactional(rollbackFor = Exception.class)
    public void takeLockStock(@LockValue Long marketId, Integer quantity) {
        boolean update = update(new LambdaUpdateWrapper<Market>()
                .setSql("sales = sales + " + quantity)
                .setSql("lock_stock = lock_stock - " + quantity)
                .ge(Market::getLockStock, quantity)
                .eq(Market::getId, marketId));
        if (!update) {
            throw new ServiceException("库存不足！");
        }
    }


    /**
     * 异步检测挂售库存
     *
     * @param marketId
     */
    @Async("threadPoolTaskExecutor")
    public void postNotifySell(Long marketId) {
        Market market = getById(marketId);
        if (market != null && market.getStatus() == MarketStatusEnum.TRADING) {
            if (market.getStock() <= 0 && market.getLockStock() <= 0) {
                soldOut(marketId);
            }
        }
    }

    /**
     * 添加市场
     *
     * @param dto
     */
    @Transactional(rollbackFor = Exception.class)
    @RedisLock(lockKey = LockKey.LOCK_MARKET)
    public void addMarket(@LockValue Long userId, AddMarketDto dto) {
        // 判断转卖价格与最低手续费
        if (dto.getPrice().compareTo(sysConfigService.getValue(Config.Sys.Market.最低交易费)) < 0) {
            throw new ServiceException("转卖单价不能低于服务费！");
        }

        // 判断是否已经达到每日售卖最大值
        Integer residueResaleCount = getResidueResaleCount(dto.getType(), userId);

        // 扳手额外处理 判断扳手是否已达到每日售卖最大值
        if (dto.getSourceId() == UserWalletEnum.WRENCH.getValue().longValue()){
            residueResaleCount = getWrenchResidueResaleCount(userId);
        }
        if (dto.getSourceId() == UserWalletEnum.LIDOU.getValue().longValue()){
            residueResaleCount = getLidouResidueResaleCount(userId);
        }
        if (residueResaleCount <= 0) {
            throw new ServiceException("已达到最大转卖次数！");
        }
        // 判断是否冷却期
        switch (dto.getType()) {
            case NORMAL:
                addNftMarket(userId, NftTypeEnum.NORMAL, dto);
                break;
            case EQUIPMENT:
                addNftMarket(userId, NftTypeEnum.EQUIPMENT, dto);
                break;
            case BOX:
                addBoxMarket(userId, dto);
                break;
            case MATERIAL:
                if(Objects.equals(dto.getSourceId(), UserWalletEnum.INTEGRAL.getValue().longValue())){
                    addIntegralMarket(userId, dto);
                    // 扳手
                }else if (Objects.equals(dto.getSourceId(), UserWalletEnum.WRENCH.getValue().longValue())){
                    addWrenchMarket(userId,dto);
                    // 力豆
                }else if (Objects.equals(dto.getSourceId(), UserWalletEnum.LIDOU.getValue().longValue())){
                    addLidouMarket(userId,dto);
                }else{
                    throw new ServiceException("暂不支持的类型！");
                }
                break;
            default:
                throw new ServiceException("暂不支持的类型！");
        }
        List<String> whiteList = sysConfigService.getValue(Config.Sys.Market.转卖次数白名单);
        if(whiteList.contains(String.valueOf(userId))){
            // 如果是白名单，则不记发布次数
            return;
        }
        if (dto.getType() == MarketTypeEnum.MATERIAL && dto.getSourceId() == UserWalletEnum.WRENCH.getValue().longValue()){
            // 扳手额外处理
            redisCache.incrementCacheMapValue(RedisKey.KEY_USER_ADD_MARKET_WRENCH_COUNT, String.valueOf(userId), 1);
        } else {
            redisCache.incrementCacheMapValue(RedisKey.KEY_USER_ADD_MARKET_COUNT + dto.getType().getValue(), String.valueOf(userId), 1);
        }
        if (dto.getSourceId() == UserWalletEnum.LIDOU.getValue().longValue()){
            // 扳手额外处理
            redisCache.incrementCacheMapValue(RedisKey.KEY_USER_ADD_MARKET_LIDOU_COUNT, String.valueOf(userId), 1);
        }
    }


    /**
     * 取消上架交易
     *
     * @param userId 卖家
     * @param marketId 市场ID
     */
    @RedisLock(lockKey = LockKey.LOCK_MARKET)
    public void cancelSaleable(Long userId, @LockValue Long marketId) {
        // 判断市场状态，如果是藏品装备，则库存不为0可以取消， 如果是宝箱，则状态不为取消可以取消
        Market market = getById(marketId);
        if (market == null || !Objects.equals(market.getUserId(), userId)) {
            throw new ServiceException("交易不存在！");
        }
        if (market.getStatus() == MarketStatusEnum.CANCEL) {
            return;
        }
        if (market.getStatus() == MarketStatusEnum.SOLD_OUT) {
            throw new ServiceException("无法取消已售完交易！");
        }
        if (market.getType() == MarketTypeEnum.NORMAL || market.getType() == MarketTypeEnum.EQUIPMENT) {
            if (market.getLockStock() > 0) {
                throw new ServiceException("订单锁定中，无法取消转卖！");
            }
            if (market.getStock() <= 0) {
                throw new ServiceException("转卖已售出，但订单处理异常，请联系客服处理！");
            }
            boolean update = update(new LambdaUpdateWrapper<Market>()
                    .set(Market::getStock, 0)
                    .set(Market::getStatus, MarketStatusEnum.CANCEL)
                    .set(Market::getCancelTime, LocalDateTime.now())
                    .eq(Market::getStock, 1)
                    .eq(Market::getLockStock, 0)
                    .eq(Market::getStatus, MarketStatusEnum.TRADING)
                    .eq(Market::getId, marketId));
            if (!update) {
                throw new ServiceException("取消失败！");
            }
            update = userNftService.updateStatus(market.getSourceId(), UserNftStatusEnum.NORMAL);
            if (!update) {
                throw new ServiceException("取消失败！");
            }
        } else if (market.getType() == MarketTypeEnum.BOX) {
            boolean update = update(new LambdaUpdateWrapper<Market>()
                    .set(Market::getStock, 0)
                    .set(Market::getStatus, MarketStatusEnum.CANCEL)
                    .set(Market::getCancelTime, LocalDateTime.now())
                    .eq(Market::getStock, market.getStock())
                    .eq(Market::getStatus, MarketStatusEnum.TRADING)
                    .eq(Market::getId, marketId));
            if (!update) {
                throw new ServiceException("取消失败！");
            }
            userBoxService.rollbackStock(market.getUserId(), market.getSourceId(), market.getStock());
        } else if(market.getType() == MarketTypeEnum.MATERIAL){
            if(Objects.equals(market.getSourceId(), UserWalletEnum.INTEGRAL.getValue().longValue())){
                // 取消热豆销售
                boolean update = update(new LambdaUpdateWrapper<Market>()
                        .set(Market::getStock, 0)
                        .set(Market::getStatus, MarketStatusEnum.CANCEL)
                        .set(Market::getCancelTime, LocalDateTime.now())
                        .eq(Market::getStock, market.getStock())
                        .eq(Market::getStatus, MarketStatusEnum.TRADING)
                        .eq(Market::getId, marketId));
                if (!update) {
                    throw new ServiceException("取消失败！");
                }
                // 回退热豆余额
                userWalletService.increase(userId, UserWalletEnum.INTEGRAL, new BigDecimal(String.valueOf(market.getStock())), WalletChangeTypeEnum.CANCEL_MARKET, market.getId(), WalletChangeTypeEnum.CANCEL_MARKET.getMsg());
            }else if (Objects.equals(market.getSourceId(), UserWalletEnum.WRENCH.getValue().longValue())){
                // 取消扳手销售
                boolean update = update(new LambdaUpdateWrapper<Market>()
                        .set(Market::getStock, 0)
                        .set(Market::getStatus, MarketStatusEnum.CANCEL)
                        .set(Market::getCancelTime, LocalDateTime.now())
                        .eq(Market::getStock, market.getStock())
                        .eq(Market::getStatus, MarketStatusEnum.TRADING)
                        .eq(Market::getId, marketId));
                if (!update) {
                    throw new ServiceException("取消失败！");
                }
                // 回退扳手余额
                userAssetWrenchRecordService.updateWrenchBalance(userId,WalletWrenchChangeTypeEnum.CANCEL_MARKET,WalletChangeDirectionEnum.IN,new BigDecimal(String.valueOf(market.getStock())),marketId,"市场转卖退回");
            } else{
                log.error("走到了不该走的地方，请处理异常！", new Exception(JSON.toJSONString(market)));
                throw new ServiceException("取消失败！");
            }
        } else {
            log.error("走到了不该走的地方，请处理异常！", new Exception(JSON.toJSONString(market)));
            throw new ServiceException("取消失败！");
        }
    }

    /**
     * 添加箱子市场
     * @param userId 卖家
     * @param dto 出售信息
     */
    private void addBoxMarket(Long userId, AddMarketDto dto) {
        // 判断单次限售数量
        Integer maxSellCount = sysConfigService.getValue(Config.Sys.Market.单次最高转卖数量);
        if (Objects.nonNull(maxSellCount)) {
            if (maxSellCount < dto.getQuantity()) {
                throw new ServiceException("单次最大转卖数量：" + maxSellCount);
            }
        }

        UserBox userBox = userBoxService.getUserBox(dto.getSourceId(), userId);
        // 判断可售的数量够不够呢
        if (userBox.getQuantity() < dto.getQuantity()) {
            throw new ServiceException("转卖数量大于持有数量！");
        } else if (userBox.getIsLock() == 1) {
            throw new ServiceException("宝盒异常，暂时无法转卖");
        }
        int usableStock = userBoxNftService.countUsableStock(userBox.getId());
        if (dto.getQuantity() > usableStock) {
            throw new ServiceException("转卖数量大于可转卖数量！");
        }

        // 判断该商品是否可以销售
        Box box = boxService.getBoxById(userBox.getBoxId());
        if (box.getIsResell() != ResellEnum.OPEN) {
            throw new ServiceException("宝箱暂未开启转卖！");
        }
        // 判断商品价格是否合理
        if (Objects.nonNull(box.getMaxPrice())) {
            // 出售最大金额
            if (dto.getPrice().compareTo(box.getMaxPrice()) > 0) {
                throw new ServiceException("最高转卖单价：￥" + box.getMaxPrice().toPlainString());
            }
        }
        if (Objects.nonNull(box.getLowestPrice())) {
            // 出售最低金额
            if (dto.getPrice().compareTo(box.getLowestPrice()) < 0) {
                throw new ServiceException("最低转卖单价：￥" + box.getLowestPrice().toPlainString());
            }
        }

        // 如果是箱子，则扣除库存
        userBoxService.takeStock(dto.getSourceId(), dto.getQuantity());

        Market market = new Market();
        market.setId(IdUtil.getSnowflakeNextId());
        market.setStatus(MarketStatusEnum.TRADING);
        market.setBoxId(userBox.getBoxId());
        market.setImg(userBox.getHeadimg());
        market.setSourceId(userBox.getId());
        market.setPrice(dto.getPrice());
        market.setSales(0);
        market.setStock(dto.getQuantity());
        market.setLockStock(0);
        market.setTotalStock(dto.getQuantity());
        market.setTitle(userBox.getName());
        market.setUserId(userId);
        market.setType(MarketTypeEnum.BOX);
        boolean save = save(market);
        if (!save) {
            throw new ServiceException("转卖失败！");
        }
    }

    /**
     * 添加藏品市场
     * @param userId 卖家
     * @param nftType 藏品类型
     * @param dto
     */
    private void addNftMarket(Long userId, NftTypeEnum nftType, AddMarketDto dto) {
        SysUserNft sysUserNft = userNftService.requireById(dto.getSourceId(), nftType, userId);
        // 检测装备状态
        if (sysUserNft.getNftStatus() == UserNftStatusEnum.USED) {
            throw new ServiceException("装备中的装备暂不可用~");
        } else if (sysUserNft.getNftStatus() == UserNftStatusEnum.SALEABLE) {
            throw new ServiceException("您的典藏已经处于转卖中~");
        } else if (sysUserNft.getNftStatus() == UserNftStatusEnum.LOCK) {
            throw new ServiceException(sysUserNft.getNftType() == NftTypeEnum.EQUIPMENT ? "装备异常，暂时无法转卖" : "藏品异常，暂时无法转卖");
        } else if (sysUserNft.getNftStatus() != UserNftStatusEnum.NORMAL) {
            throw new ServiceException("当前装备暂不可用~");
        }
        if (Objects.nonNull(sysUserNft.getSaleableTime())) {
            if (LocalDateTime.now().isBefore(sysUserNft.getSaleableTime())) {
                if (dto.getType() == MarketTypeEnum.NORMAL) {
                    throw new ServiceException("转卖冷却中，暂时不能出售！");
                } else {
                    throw new ServiceException("转卖冷却中，暂时不能出售！");
                }
            }
        }

        // 判断该商品是否可以销售
        Nft nft = nftService.requireNft(sysUserNft.getNftId());
        if (nft.getIsResell() != ResellEnum.OPEN) {
            throw new ServiceException("暂未开启转卖！");
        }

        if(nftType == NftTypeEnum.NORMAL){
            // 判断商品价格是否合理
            if (Objects.nonNull(nft.getMaxPrice())) {
                // 出售最大金额
                if (dto.getPrice().compareTo(nft.getMaxPrice()) > 0) {
                    throw new ServiceException("最高转卖单价：￥" + nft.getMaxPrice().toPlainString());
                }
            }
            if (Objects.nonNull(nft.getLowestPrice())) {
                // 出售最低金额
                if (dto.getPrice().compareTo(nft.getLowestPrice()) < 0) {
                    throw new ServiceException("最低转卖单价：￥" + nft.getLowestPrice().toPlainString());
                }
            }
        }else{
            // 获取装备等级信息，根据等级判断装备价格
            EquipmentLevelPrice levelPrice = equipmentLevelPriceService.getEquipmentLevelPrice(sysUserNft.getNftId(), sysUserNft.getNftNumberId());
            if(Objects.isNull(levelPrice)){
                throw new ServiceException("暂未开启转卖！");
            }
            if(Objects.nonNull(levelPrice.getMaxPrice())){
                // 出售最大金额
                if (dto.getPrice().compareTo(levelPrice.getMaxPrice()) > 0) {
                    throw new ServiceException("最高转卖单价：￥" + levelPrice.getMaxPrice().toPlainString());
                }
            }
            if(Objects.nonNull(levelPrice.getLowestPrice())){
                // 出售最低金额
                if (dto.getPrice().compareTo(levelPrice.getLowestPrice()) < 0) {
                    throw new ServiceException("最低转卖单价：￥" + levelPrice.getLowestPrice().toPlainString());
                }
            }
        }
        // 装备是否是全新
        boolean isNew = false;
        if(nftType == NftTypeEnum.EQUIPMENT){
            Equipment equipment = equipmentService.getByNftNumberId(sysUserNft.getNftNumberId());
            isNew = (equipment.getBreakage().compareTo(equipment.getTotalBreakage()) == 0 && equipment.getDurability().compareTo(equipment.getTotalDurability()) == 0);
        }
        // 改变藏品状态
        userNftService.updateStatus(sysUserNft.getId(), UserNftStatusEnum.SALEABLE);

        NftNumber nftNumber = nftNumberService.getNftNumber(sysUserNft.getNftNumberId());

        Market market = new Market();
        market.setId(IdUtil.getSnowflakeNextId());
        market.setNftId(sysUserNft.getNftId());
        market.setNftNumberId(sysUserNft.getNftNumberId());
        market.setNumberValueStr(nftNumber.getNumberValueStr());
        market.setStatus(MarketStatusEnum.TRADING);
        market.setIsNew(isNew);
        market.setImg(nft.getMainImg());
        market.setSourceId(sysUserNft.getId());
        market.setPrice(dto.getPrice());
        market.setSales(0);
        market.setStock(1);
        market.setLockStock(0);
        market.setTotalStock(1);
        market.setTitle(nft.getTitle());
        market.setUserId(userId);
        market.setType(dto.getType());
        boolean save = save(market);
        if (!save) {
            throw new ServiceException("转卖失败！");
        }
    }

    /**
     * 添加热豆市场
     * @param userId 卖家ID
     * @param dto    出售信息
     */
    private void addIntegralMarket(Long userId, AddMarketDto dto) {
        // 判断单次限售数量
        Integer maxSellCount = sysConfigService.getValue(Config.Sys.Treasury.单次最高转卖数量);
        if (Objects.nonNull(maxSellCount)) {
            if (maxSellCount < dto.getQuantity()) {
                throw new ServiceException("单次最大转卖数量：" + maxSellCount);
            }
        }
        Integer minSellCount = sysConfigService.getValue(Config.Sys.Treasury.单次最低转卖数量);
        if (Objects.nonNull(minSellCount)) {
            if (minSellCount > dto.getQuantity()) {
                throw new ServiceException("单次最低转卖数量：" + minSellCount);
            }
        }

        SysUserWallet wallet = userWalletService.getWallet(userId, UserWalletEnum.INTEGRAL);
        // 判断可售的数量够不够呢
        if (wallet.getAmount().compareTo(new BigDecimal(dto.getQuantity())) < 0) {
            throw new ServiceException("转卖数量大于持有数量！");
        }

        // 判断该商品是否可以销售
        Boolean resell = sysConfigService.getValue(Config.Sys.Treasury.转卖开关);
        if (BooleanUtils.isNotTrue(resell)) {
            throw new ServiceException("暂未开启热豆转卖！");
        }
        // 判断商品价格是否合理
        BigDecimal maxPrice = sysConfigService.getValue(Config.Sys.Treasury.热豆最高价);
        BigDecimal minPrice = sysConfigService.getValue(Config.Sys.Treasury.热豆最低价);
        if (Objects.nonNull(maxPrice)) {
            // 出售最大金额
            if (dto.getPrice().compareTo(maxPrice) > 0) {
                throw new ServiceException("最高转卖单价：￥" + maxPrice.toPlainString());
            }
        }
        if (Objects.nonNull(minPrice)) {
            // 出售最低金额
            if (dto.getPrice().compareTo(minPrice) < 0) {
                throw new ServiceException("最低转卖单价：￥" + minPrice.toPlainString());
            }
        }

        // 扣除库存
        Long marketId = IdUtil.getSnowflakeNextId();
        userWalletService.reduce(userId, UserWalletEnum.INTEGRAL, new BigDecimal(String.valueOf(dto.getQuantity())), WalletChangeTypeEnum.ADD_MARKET, marketId, WalletChangeTypeEnum.ADD_MARKET.getMsg());

        Market market = new Market();
        market.setId(marketId);
        market.setStatus(MarketStatusEnum.TRADING);
        market.setImg(sysConfigService.getValue(Config.Sys.Treasury.热豆商品图));
        market.setSourceId(dto.getSourceId());
        market.setPrice(dto.getPrice());
        market.setSales(0);
        market.setStock(dto.getQuantity());
        market.setLockStock(0);
        market.setTotalStock(dto.getQuantity());
        market.setTitle("热豆");
        market.setUserId(userId);
        market.setType(MarketTypeEnum.MATERIAL);
        boolean save = save(market);
        if (!save) {
            throw new ServiceException("转卖失败！");
        }
    }

    /**
     * 添加扳手市场
     */
    private void addWrenchMarket(Long userId, AddMarketDto dto) {
        // 判断单次限售数量
        Integer maxSellCount = sysConfigService.getValue(Config.Sys.Treasury.扳手单次最高转卖数量);
        if (Objects.nonNull(maxSellCount)) {
            if (maxSellCount < dto.getQuantity()) {
                throw new ServiceException("扳手单次最大转卖数量：" + maxSellCount);
            }
        }
        Integer minSellCount = sysConfigService.getValue(Config.Sys.Treasury.扳手单次最低转卖数量);
        if (Objects.nonNull(minSellCount)) {
            if (minSellCount > dto.getQuantity()) {
                throw new ServiceException("扳手单次最低转卖数量：" + minSellCount);
            }
        }
        UserAssetWrench wrench = userAssetWrenchService.getByUserId(userId);
        // 判断出售数量是否大于余额
        if (wrench.getAmount().compareTo(new BigDecimal(dto.getQuantity())) < 0) {
            throw new ServiceException("扳手转卖数量大于持有数量！");
        }
        // 判断该商品是否可以销售
        Boolean resell = sysConfigService.getValue(Config.Sys.Treasury.扳手转卖开关);
        if (BooleanUtils.isNotTrue(resell)) {
            throw new ServiceException("暂未开启扳手转卖！");
        }

        // 判断商品价格是否合理
        BigDecimal maxPrice = sysConfigService.getValue(Config.Sys.Treasury.扳手最高价);
        BigDecimal minPrice = sysConfigService.getValue(Config.Sys.Treasury.扳手最低价);
        if (Objects.nonNull(maxPrice)) {
            // 出售最大金额
            if (dto.getPrice().compareTo(maxPrice) > 0) {
                throw new ServiceException("扳手最高转卖单价：￥" + maxPrice.toPlainString());
            }
        }
        if (Objects.nonNull(minPrice)) {
            // 出售最低金额
            if (dto.getPrice().compareTo(minPrice) < 0) {
                throw new ServiceException("扳手最低转卖单价：￥" + minPrice.toPlainString());
            }
        }
        // 扣除库存
        Long marketId = IdUtil.getSnowflakeNextId();
        userAssetWrenchRecordService.updateWrenchBalance(userId, WalletWrenchChangeTypeEnum.MARKET_RESALE, WalletChangeDirectionEnum.OUT,BigDecimal.valueOf(dto.getQuantity()),dto.getSourceId(),"市场转卖");
        Market market = new Market();
        market.setId(marketId);
        market.setStatus(MarketStatusEnum.TRADING);
        market.setImg(sysConfigService.getValue(Config.Sys.Treasury.扳手商品图));
        market.setSourceId(dto.getSourceId());
        market.setPrice(dto.getPrice());
        market.setSales(0);
        market.setStock(dto.getQuantity());
        market.setLockStock(0);
        market.setTotalStock(dto.getQuantity());
        market.setTitle("扳手");
        market.setUserId(userId);
        market.setType(MarketTypeEnum.MATERIAL);
        boolean save = save(market);
        if (!save) {
            throw new ServiceException("转卖失败！");
        }
    }

    /**
     * 添加力豆市场
     */
    private void addLidouMarket(Long userId, AddMarketDto dto) {
        // 判断单次限售数量
        Integer maxSellCount = sysConfigService.getValue(Config.Sys.Treasury.力豆单次最高转卖数量);
        if (Objects.nonNull(maxSellCount)) {
            if (maxSellCount < dto.getQuantity()) {
                throw new ServiceException("力豆单次最大转卖数量：" + maxSellCount);
            }
        }
        Integer minSellCount = sysConfigService.getValue(Config.Sys.Treasury.力豆单次最低转卖数量);
        if (Objects.nonNull(minSellCount)) {
            if (minSellCount > dto.getQuantity()) {
                throw new ServiceException("力豆单次最低转卖数量：" + minSellCount);
            }
        }
        UserAssetLidou lidou = userAssetLidouService.getLidouInfo(userId);
        // 判断出售数量是否大于余额
        if (lidou.getAmount().compareTo(new BigDecimal(dto.getQuantity())) < 0) {
            throw new ServiceException("力豆转卖数量大于持有数量！");
        }
        // 判断该商品是否可以销售
        Boolean resell = sysConfigService.getValue(Config.Sys.Treasury.力豆转卖开关);
        if (BooleanUtils.isNotTrue(resell)) {
            throw new ServiceException("暂未开启力豆转卖！");
        }

        // 判断商品价格是否合理
        BigDecimal maxPrice = sysConfigService.getValue(Config.Sys.Treasury.力豆最高价);
        BigDecimal minPrice = sysConfigService.getValue(Config.Sys.Treasury.力豆最低价);
        if (Objects.nonNull(maxPrice)) {
            // 出售最大金额
            if (dto.getPrice().compareTo(maxPrice) > 0) {
                throw new ServiceException("力豆最高转卖单价：￥" + maxPrice.toPlainString());
            }
        }
        if (Objects.nonNull(minPrice)) {
            // 出售最低金额
            if (dto.getPrice().compareTo(minPrice) < 0) {
                throw new ServiceException("力豆最低转卖单价：￥" + minPrice.toPlainString());
            }
        }
        // 扣除库存
        Long marketId = IdUtil.getSnowflakeNextId();
        userAssetWrenchRecordService.updateWrenchBalance(userId, WalletWrenchChangeTypeEnum.MARKET_RESALE, WalletChangeDirectionEnum.OUT,BigDecimal.valueOf(dto.getQuantity()),dto.getSourceId(),"市场转卖");
        Market market = new Market();
        market.setId(marketId);
        market.setStatus(MarketStatusEnum.TRADING);
        market.setImg(sysConfigService.getValue(Config.Sys.Treasury.力豆商品图));
        market.setSourceId(dto.getSourceId());
        market.setPrice(dto.getPrice());
        market.setSales(0);
        market.setStock(dto.getQuantity());
        market.setLockStock(0);
        market.setTotalStock(dto.getQuantity());
        market.setTitle("力豆");
        market.setUserId(userId);
        market.setType(MarketTypeEnum.MATERIAL);
        boolean save = save(market);
        if (!save) {
            throw new ServiceException("转卖失败！");
        }
    }

    /**
     * 市场转卖记录（市场状态为 转卖中，已取消）
     *
     * @return
     */
    public R resellRecord(MarketResellDto params, Long userId) {
        if (StringUtils.isEmpty(params.getOrderByColumn())) {
            //如果市场状态为转卖中，则按照创建时间倒序；如果市场状态为已取消，则按照取消时间倒序
            if (params.getStatus() == MarketStatusEnum.CANCEL) {
                params.setOrderByColumn("cancelTime");
            } else {
                params.setOrderByColumn("createTime");
            }
            params.setSortAsc(false);
        }
        params.startPageAndOrder();
        //服务费：总价 × 服务费比例
        BigDecimal transactionFeeRatio = sysConfigService.getValue(Config.Sys.Market.交易费率);
        switch (params.getType()) {
            case NORMAL:
            case EQUIPMENT:
                LambdaQueryWrapper<Market> wp = new LambdaQueryWrapper<>();
                wp.eq(Market::getUserId, userId);
                wp.eq(Market::getType, params.getType());
                if (params.getStatus() == MarketStatusEnum.TRADING) {
                    wp.gt(Market::getStock, 0);
                }
                if (StrUtil.isNotBlank(params.getSearch())) {
                    wp.eq(Market::getId,  params.getSearch());
                }
                if(Objects.nonNull(params.getCreateTimeStart())){
                    wp.ge(Market::getCreateTime, params.getCreateTimeStart());
                }
                if(Objects.nonNull(params.getCreateTimeEnd())){
                    wp.le(Market::getCreateTime, params.getCreateTimeEnd());
                }
                wp.eq(Market::getStatus, params.getStatus());

                List<Market> normalList = list(wp);
                List<MarketNormalResellVo> normalResellVo = BeanUtil.copyToList(normalList, MarketNormalResellVo.class);
                for (MarketNormalResellVo item : normalResellVo) {
                    //预计收入：总价 － （总价）× 服务费比例；总价：单价 × 数量
                    item.setEstimatePrice(item.getPrice().subtract(item.getPrice().multiply(transactionFeeRatio)
                            .setScale(2, RoundingMode.HALF_UP)));
                }
                return R.ok(normalResellVo, normalList);

            case BOX:
            case MATERIAL:
                LambdaQueryWrapper<Market> boxwp = new LambdaQueryWrapper<>();
                // 区分热豆和扳手
                if (params.getSourceId() !=null){
                    boxwp.eq(Market::getSourceId,params.getSourceId());
                }
                boxwp.eq(Market::getUserId, userId);
                boxwp.eq(Market::getType, params.getType());
                if (params.getStatus() == MarketStatusEnum.TRADING) {
                    boxwp.gt(Market::getStock, 0);
                }
                if (StrUtil.isNotBlank(params.getSearch())) {
                    boxwp.eq(Market::getId,  params.getSearch());
                }
                if(Objects.nonNull(params.getCreateTimeStart())){
                    boxwp.ge(Market::getCreateTime, params.getCreateTimeStart());
                }
                if(Objects.nonNull(params.getCreateTimeEnd())){
                    boxwp.le(Market::getCreateTime, params.getCreateTimeEnd());
                }
                boxwp.eq(Market::getStatus, params.getStatus());

                List<Market> boxList = list(boxwp);
                List<MarketBoxResellVo> boxResellVos = BeanUtil.copyToList(boxList, MarketBoxResellVo.class);
                for (MarketBoxResellVo item : boxResellVos) {
                    //预计收入：总价 － （总价）× 服务费比例；总价：单价 × 数量
                    BigDecimal stockDecimal = new BigDecimal(item.getTotalStock());
                    item.setEstimatePrice(item.getPrice().multiply(stockDecimal).subtract(item.getPrice().multiply(stockDecimal).multiply(transactionFeeRatio)
                            .setScale(2, RoundingMode.HALF_UP)));
                }
                return R.ok(boxResellVos, boxList);

            default:
                throw new ServiceException("市场类型错误");
        }
    }

    /**
     * 预添加市场
     *
     * @param userId
     * @param dto
     */
    public PreAddMarketVo preAddInfo(Long userId, PreAddMarketDto dto) {
        Integer residueResaleCount = getResidueResaleCount(dto.getType(), userId);
        switch (dto.getType()) {
            case NORMAL:
            case EQUIPMENT: {
                // 判断藏品是否存在
                NftTypeEnum nftType;
                if (dto.getType() == MarketTypeEnum.NORMAL) {
                    nftType = NftTypeEnum.NORMAL;
                } else {
                    nftType = NftTypeEnum.EQUIPMENT;
                }
                SysUserNft sysUserNft = userNftService.requireById(dto.getSourceId(), nftType, userId);
                PreAddMarketVo vo = new PreAddMarketVo();
                // 设置可售数量相关
                vo.setStock(1);
                LocalDateTime saleableTime = sysUserNft.getSaleableTime();
                if (Objects.nonNull(saleableTime)) {
                    vo.setSaleableTime(saleableTime);
                    LocalDateTime now = LocalDateTime.now();
                    if (now.isBefore(saleableTime)) {
                        vo.setFreezeStock(1);
                        vo.setUsableStock(0);
                        vo.setSaleableCountDown((int) LocalDateTimeUtil.between(now, saleableTime, ChronoUnit.MILLIS));
                    } else {
                        vo.setFreezeStock(0);
                        vo.setUsableStock(1);
                        vo.setSaleableCountDown(0);
                    }
                }
                vo.setResaleCountMax(1);

                // 设置剩余转卖次数
                vo.setResidueResaleCount(Math.max(residueResaleCount, 0));

                //设置交易费率
                vo.setTransactionFeeRatio(sysConfigService.getValue(Config.Sys.Market.交易费率));
                vo.setTransactionFeeMin(sysConfigService.getValue(Config.Sys.Market.最低交易费));

                // 设置交易价格区间
                Nft nft = nftService.requireNft(sysUserNft.getNftId());
                vo.setSaleable(nft.getIsResell() == ResellEnum.OPEN);
                if(BooleanUtils.isTrue(vo.getSaleable())){
                    if (dto.getType() == MarketTypeEnum.EQUIPMENT) {
                        EquipmentLevelPrice levelPrice = equipmentLevelPriceService.getEquipmentLevelPrice(nft.getId(), sysUserNft.getNftNumberId());
                        if(levelPrice == null){
                            vo.setSaleable(false);
                        }else{
                            vo.setSellPriceMax(levelPrice.getMaxPrice());
                            vo.setSellPriceMin(levelPrice.getLowestPrice());
                        }
                    }else{
                        vo.setSellPriceMax(nft.getMaxPrice());
                        vo.setSellPriceMin(nft.getLowestPrice());
                    }
                }
                return vo;
            }
            case BOX: {
                // 判断宝箱是否存在
                UserBox userBox = userBoxService.getUserBox(dto.getSourceId(), userId);
                PreAddMarketVo vo = new PreAddMarketVo();
                // 设置可售数量相关
                vo.setStock(userBox.getQuantity());
                vo.setSaleableTime(LocalDateTime.now());

                // 获取用户当前可售宝箱数量
                int usableStock = userBoxNftService.countUsableStock(userBox.getId());
                // 获取用户当前冻结宝箱数量
                int freezeStock = userBoxNftService.countFreezeStock(userBox.getId());
                vo.setFreezeStock(freezeStock);
                vo.setUsableStock(usableStock);
                vo.setSaleableCountDown(0);
                vo.setResaleCountMax(sysConfigService.getValue(Config.Sys.Market.单次最高转卖数量));

                // 设置剩余转卖次数
                vo.setResidueResaleCount(residueResaleCount);

                //设置交易费率
                vo.setTransactionFeeRatio(sysConfigService.getValue(Config.Sys.Market.交易费率));
                vo.setTransactionFeeMin(sysConfigService.getValue(Config.Sys.Market.最低交易费));

                // 设置交易价格区间
                Box box = boxService.getBoxById(userBox.getBoxId());
                vo.setSaleable(box.getIsResell() == ResellEnum.OPEN);
                vo.setSellPriceMax(box.getMaxPrice());
                vo.setSellPriceMin(box.getLowestPrice());
                return vo;
            }
            case MATERIAL:
                PreAddMarketVo vo = new PreAddMarketVo();
                // 材料，判断ID为2表示热豆
                if (Objects.equals(dto.getSourceId(), UserWalletEnum.INTEGRAL.getValue().longValue())) {
                    // 获取钱包余额
                    SysUserWallet wallet = userWalletService.getWallet(userId, UserWalletEnum.INTEGRAL);
                    // 设置可售数量相关
                    vo.setStock(wallet.getAmount().setScale(0, RoundingMode.DOWN).intValue());
                    vo.setSaleableTime(LocalDateTime.now());
                    // 获取用户当前可售热豆数量
                    vo.setFreezeStock(0);
                    vo.setUsableStock(vo.getStock());
                    vo.setSaleableCountDown(0);

                    vo.setResaleCountMax(sysConfigService.getValue(Config.Sys.Treasury.单次最高转卖数量));
                    vo.setResaleCountMin(sysConfigService.getValue(Config.Sys.Treasury.单次最低转卖数量));

                    // 设置剩余转卖次数
                    vo.setResidueResaleCount(residueResaleCount);

                    //设置交易费率
                    vo.setTransactionFeeRatio(sysConfigService.getValue(Config.Sys.Market.交易费率));
                    vo.setTransactionFeeMin(sysConfigService.getValue(Config.Sys.Market.最低交易费));

                    // 设置交易价格区间
                    vo.setSaleable(sysConfigService.getValue(Config.Sys.Treasury.转卖开关));
                    vo.setSellPriceMax(sysConfigService.getValue(Config.Sys.Treasury.热豆最高价));
                    vo.setSellPriceMin(sysConfigService.getValue(Config.Sys.Treasury.热豆最低价));
                    return vo;
                } else if(Objects.equals(dto.getSourceId(), UserWalletEnum.WRENCH.getValue().longValue())){
                    // 材料 id为10表示扳手
                    // 获取扳手转卖次数
                    Integer wrenchResidueResaleCount = getWrenchResidueResaleCount(userId);
                    // 获取扳手余额
                    UserAssetWrench wrench = userAssetWrenchService.getByUserId(userId);
                    // 设置可售数量相关
                    vo.setStock(wrench.getAmount().setScale(0, RoundingMode.DOWN).intValue());
                    vo.setSaleableTime(LocalDateTime.now());
                    // 获取用户当前可售扳手数量
                    vo.setFreezeStock(0);
                    vo.setUsableStock(vo.getStock());
                    vo.setSaleableCountDown(0);

                    vo.setResaleCountMax(sysConfigService.getValue(Config.Sys.Treasury.扳手单次最高转卖数量));
                    vo.setResaleCountMin(sysConfigService.getValue(Config.Sys.Treasury.扳手单次最低转卖数量));

                    // 设置剩余转卖次数
                    vo.setResidueResaleCount(wrenchResidueResaleCount);

                    //设置交易费率
                    vo.setTransactionFeeRatio(sysConfigService.getValue(Config.Sys.Market.交易费率));
                    vo.setTransactionFeeMin(sysConfigService.getValue(Config.Sys.Market.最低交易费));

                    // 设置交易价格区间
                    vo.setSaleable(sysConfigService.getValue(Config.Sys.Treasury.扳手转卖开关));
                    vo.setSellPriceMax(sysConfigService.getValue(Config.Sys.Treasury.扳手最高价));
                    vo.setSellPriceMin(sysConfigService.getValue(Config.Sys.Treasury.扳手最低价));
                    return vo;
                } else if(Objects.equals(dto.getSourceId(), UserWalletEnum.LIDOU.getValue().longValue())){
                    // 材料 id为3表示力豆
                    // 获取力豆转卖次数
                    Integer wrenchResidueResaleCount = getLidouResidueResaleCount(userId);
                    // 获取力豆余额
                    UserAssetLidou lidou = userAssetLidouService.getLidouInfo(userId);
                    // 设置可售数量相关
                    vo.setStock(lidou.getAmount().setScale(0, RoundingMode.DOWN).intValue());
                    vo.setSaleableTime(LocalDateTime.now());
                    // 获取用户当前可售力豆数量
                    vo.setFreezeStock(0);
                    vo.setUsableStock(vo.getStock());
                    vo.setSaleableCountDown(0);

                    vo.setResaleCountMax(sysConfigService.getValue(Config.Sys.Treasury.力豆单次最高转卖数量));
                    vo.setResaleCountMin(sysConfigService.getValue(Config.Sys.Treasury.力豆单次最低转卖数量));

                    // 设置剩余转卖次数
                    vo.setResidueResaleCount(wrenchResidueResaleCount);

                    //设置交易费率
                    vo.setTransactionFeeRatio(sysConfigService.getValue(Config.Sys.Market.交易费率));
                    vo.setTransactionFeeMin(sysConfigService.getValue(Config.Sys.Market.最低交易费));

                    // 设置交易价格区间
                    vo.setSaleable(sysConfigService.getValue(Config.Sys.Treasury.力豆转卖开关));
                    vo.setSellPriceMax(sysConfigService.getValue(Config.Sys.Treasury.力豆最高价));
                    vo.setSellPriceMin(sysConfigService.getValue(Config.Sys.Treasury.力豆最低价));
                    return vo;
                }else{
                    throw new ServiceException("暂不支持的类型");
            }
            default:
                throw new ServiceException("暂不支持的类型！");
        }

    }

    /**
     * 市场状态为（交易中，已售完）单独处理 查订单表
     *
     * @param userId
     * @return
     */
    public R resellRecord2(MarketResellDto params, Long userId) {
        if (StringUtils.isEmpty(params.getOrderByColumn())) {
            params.setOrderByColumn("createTime");
            params.setSortAsc(false);
        }
        params.startPageAndOrder();
        //服务费：总价 × 服务费比例
        BigDecimal transactionFeeRatio = sysConfigService.getValue(Config.Sys.Market.交易费率);
        switch (params.getType()) {
            case NORMAL:
            case EQUIPMENT:
                List<MarketNormalResellVo> normalResellVo = orderMainMapper.resellNormalAndEquipmentRecord(params, userId);
                for (MarketNormalResellVo item : normalResellVo) {
                    //计算预估收入
                    item.setEstimatePrice(item.getPrice().subtract(item.getTransactionFee()));
                    if (item.getOrderStatus() == OrderStatusEnum.UN_PAY) {//交易中
                        item.setStatus(MarketStatusEnum.TRADE);
                    } else if (item.getOrderStatus() == OrderStatusEnum.COMPLETED) { //已售完
                        item.setStatus(MarketStatusEnum.SOLD_OUT);
                    }
                }
                return R.ok(normalResellVo);

            case BOX:
            case MATERIAL:
                if (params.getStatus().equals(MarketStatusEnum.TRADE)) {
                    throw new ServiceException("宝盒暂无交易中");
                }
                LambdaQueryWrapper<Market> wp = new LambdaQueryWrapper<>();
                if (params.getSourceId() !=null){
                    wp.eq(Market::getSourceId,params.getSourceId());
                }
                wp.eq(Market::getUserId, userId);
                wp.eq(Market::getType, params.getType());
                wp.eq(Market::getStock, 0);
                wp.eq(Market::getStatus, MarketStatusEnum.SOLD_OUT);
                if (StrUtil.isNotBlank(params.getSearch())) {
                    wp.eq(Market::getId, params.getSearch());
                }
                if(Objects.nonNull(params.getCreateTimeStart())){
                    wp.ge(Market::getCreateTime, params.getCreateTimeStart());
                }
                if(Objects.nonNull(params.getCreateTimeEnd())){
                    wp.le(Market::getCreateTime, params.getCreateTimeEnd());
                }

                List<Market> boxList = list(wp);

                List<MarketBoxResellVo> boxResellVos = BeanUtil.copyToList(boxList, MarketBoxResellVo.class);
                for (MarketBoxResellVo item : boxResellVos) {
                    //预计收入：总价 － （总价）× 服务费比例；总价：单价 × 数量
                    BigDecimal stockDecimal = new BigDecimal(item.getTotalStock());
                    item.setEstimatePrice(item.getPrice().multiply(stockDecimal).subtract(item.getPrice().multiply(stockDecimal).multiply(transactionFeeRatio)
                            .setScale(2, RoundingMode.HALF_UP)));
                    //总价
                    item.setTotalPrice(item.getPrice().multiply(stockDecimal).setScale(2, RoundingMode.HALF_UP));
                }
                return R.ok(boxResellVos, boxList);
            default:
                throw new ServiceException("市场类型错误");
        }
    }

    /**
     * 转卖记录 宝盒，材料详情
     *
     * @return
     */
    public R<List<MarketResellBoxInfoVo>> resellRecordBoxInfo(MarketBoxInfoDto params, Long userId) {
        params.startPage();
        List<MarketResellBoxInfoVo> vo = orderMainMapper.resellRecordBoxInfo(params, userId);
        for (MarketResellBoxInfoVo item : vo) {
            if (item.getOrderStatus() == OrderStatusEnum.UN_PAY) {//订单待付款 = 市场交易中
                item.setStatus(MarketStatusEnum.TRADE);
            } else if (item.getOrderStatus() == OrderStatusEnum.COMPLETED) {//订单已完成 = 市场已售完
                item.setStatus(MarketStatusEnum.SOLD_OUT);
            }
        }
        return R.ok(vo);
    }

    /**
     * 将市场置为已售完
     *
     * @param marketId
     */
    public void soldOut(Long marketId) {
        update(new LambdaUpdateWrapper<Market>()
                .set(Market::getStatus, MarketStatusEnum.SOLD_OUT)
                .set(Market::getFinishTime, LocalDateTime.now())
                .eq(Market::getId, marketId));
    }

    public R<MarketInfoVo> marketNftInfo(Long marketId) {
        Market market = getById(marketId);
        if (Objects.isNull(market)) {
            return R.fail("集市不存在");
        }
        MarketInfoVo vo = new MarketInfoVo();
        switch (market.getType()) {
            case NORMAL:
                vo = baseMapper.marketNftInfo(marketId);
                break;
            case EQUIPMENT:
                vo = baseMapper.marketNftInfo(marketId);
                //设置equipment的打卡空投热豆
                if (Objects.nonNull(vo) && Objects.nonNull(vo.getEquipment())) {
                    EquipmentInfoVo equipment = vo.getEquipment();
                    equipment.setPunchAirdropBeans(nftService.getPunchAirdropBeans(vo.getId(), vo.getEquipment().getLuck()));
//                    vo.getEquipment().setPunchAirdropBeans(nftService.getPunchAirdropBeans(vo.getId(), vo.getEquipment().getLuck()));
                    // 设置装备剩余空投与打卡次数
                    equipment.setRemainAirdrop(userPunchService.calcPunchIncomeByWeaken(BeanUtil.copyProperties(equipment, Equipment.class), false));
                    equipment.setRemainPunch(userPunchService.calcPunchCountByBreakage(equipment.getBreakage()));
                }
                break;

            case BOX:
                vo = baseMapper.marketBoxInfo(marketId);
                break;
        }
        vo.setBuyKnow(sysConfigService.getValue(Config.Sys.Nft.购买须知));
        vo.setCopyrightNotice(sysConfigService.getValue(Config.Sys.Nft.版权声明));
        return R.ok(vo);
    }

    /**
     * 获取市场剩余转卖次数
     *
     * @param type
     * @return
     */
    public Integer getResidueResaleCount(MarketTypeEnum type, Long userId) {
        // 已使用次数
        Integer todaySellTime = redisCache.getCacheMapValue(RedisKey.KEY_USER_ADD_MARKET_COUNT + type.getValue(), String.valueOf(userId));
        Integer resaleMax = null;
        switch (type) {
            case NORMAL:
                resaleMax = sysConfigService.getValue(Config.Sys.Market.典藏_每日转卖次数);
                break;
            case EQUIPMENT:
                resaleMax = sysConfigService.getValue(Config.Sys.Market.装备_每日转卖次数);
                break;
            case BOX:
                resaleMax = sysConfigService.getValue(Config.Sys.Market.宝盒_每日转卖次数);
                break;
            case MATERIAL:
                resaleMax = sysConfigService.getValue(Config.Sys.Market.热豆_每日转卖次数);
                break;
        }
        Integer residueResaleCount;
        if (Objects.nonNull(todaySellTime)) {
            residueResaleCount = resaleMax - todaySellTime;
        } else {
            residueResaleCount = resaleMax;
        }
        return residueResaleCount;
    }

    /**
     * 获取市场剩余转卖扳手次数
     */
    public Integer getWrenchResidueResaleCount(Long userId){
        // 已使用次数
        Integer todaySellTime = redisCache.getCacheMapValue(RedisKey.KEY_USER_ADD_MARKET_WRENCH_COUNT, String.valueOf(userId));
        // 扳手每日最大转卖次数
        Integer resaleMax = sysConfigService.getValue(Config.Sys.Market.扳手_每日转卖次数);

        Integer residueResaleCount;
        // 如果已有使用次数,则剩余次数为最大减去已使用,反之则就等于最大次数
        if (Objects.nonNull(todaySellTime)){
            residueResaleCount = resaleMax-todaySellTime;
        }else {
            residueResaleCount=resaleMax;
        }
        return residueResaleCount;
    }

    /**
     * 获取市场剩余转卖力豆次数
     */
    public Integer getLidouResidueResaleCount(Long userId){
        // 已使用次数
        Integer todaySellTime = redisCache.getCacheMapValue(RedisKey.KEY_USER_ADD_MARKET_LIDOU_COUNT, String.valueOf(userId));
        // 扳手每日最大转卖次数
        Integer resaleMax = sysConfigService.getValue(Config.Sys.Market.力豆_每日转卖次数);

        Integer residueResaleCount;
        // 如果已有使用次数,则剩余次数为最大减去已使用,反之则就等于最大次数
        if (Objects.nonNull(todaySellTime)){
            residueResaleCount = resaleMax-todaySellTime;
        }else {
            residueResaleCount=resaleMax;
        }
        return residueResaleCount;
    }
    /**
     * 统计出售中的总库存数
     *
     * @param userId
     * @param marketTypeEnum
     * @param userWalletEnum
     * @return
     */
    public int getSaleStock(Long userId, MarketTypeEnum marketTypeEnum, UserWalletEnum userWalletEnum) {
        List<Market> list = list(new LambdaQueryWrapper<Market>()
                .select(Market::getStock, Market::getLockStock)
                .eq(Market::getUserId, userId)
                .eq(Market::getType, marketTypeEnum)
                .eq(Market::getSourceId, userWalletEnum.getValue()));
        return list.stream().reduce(0, (sum, market) -> sum + market.getStock() + market.getLockStock(), (a, b) -> null);
    }

    /**
     * 统计出售中的扳手总库存输
     * @param userId
     * @param marketTypeEnum
     * @param userWalletEnum
     * @return
     */
    public int getWrenchSaleStock(Long userId, MarketTypeEnum marketTypeEnum, UserWalletEnum userWalletEnum) {
        List<Market> wrenchList = list(new LambdaQueryWrapper<Market>()
                .select(Market::getStock, Market::getLockStock)
                .eq(Market::getUserId, userId)
                .eq(Market::getType, marketTypeEnum)
                .eq(Market::getSourceId, userWalletEnum.getValue()));
        return wrenchList.stream().reduce(0, (sum, market) -> sum + market.getStock() + market.getLockStock(), (a, b) -> null);
    }

    /**
     * web端用户详情转卖记录详情
     * @param dto
     * @param userId
     * @return
     */
    public R<List<WebMarketResellBoxInfoVo>> webResellRecordBoxInfo(MarketBoxInfoDto dto, Long userId) {
        dto.startPage();
        List<WebMarketResellBoxInfoVo> vo = orderMainMapper.webResellRecordBoxInfo(dto, userId);
        for (WebMarketResellBoxInfoVo item : vo) {
            if (item.getOrderStatus() == OrderStatusEnum.UN_PAY) {//订单待付款 = 市场交易中
                item.setStatus(MarketStatusEnum.TRADE);
            } else if (item.getOrderStatus() == OrderStatusEnum.COMPLETED) {//订单已完成 = 市场已售完
                item.setStatus(MarketStatusEnum.SOLD_OUT);
            } else if (item.getOrderStatus() == OrderStatusEnum.CANCEL) {//订单已取消 = 市场已取消
                item.setStatus(MarketStatusEnum.CANCEL);
            }
        }
        return R.ok(vo);
    }

    /**
     * 市场顶部-市场热豆、扳手交易最低价
     * @return
     */
    public R<HotBeansVo> hotBeansMinPrice() {
        HotBeansVo vo = baseMapper.hotBeansMinPrice();
        vo.setWrenchMinPrice(baseMapper.wrenchMinPrice());
        return R.ok(vo);
    }

    /**
     * 市场下架
     * @param marketType
     * @param sourceId
     */
    public void offShelf(MarketTypeEnum marketType, Long sourceId) {
        List<Market> list = list(Wrappers.<Market>lambdaQuery()
                .select(Market::getId, Market::getUserId)
                .eq(Market::getType, marketType)
                .eq(Market::getSourceId, sourceId)
                .eq(Market::getStatus, MarketStatusEnum.TRADING));
        list.forEach(market -> {
            try {
                cancelSaleable(market.getUserId(), market.getId());
            }catch (Exception e){
                log.info(e.getMessage());
            }
        });
    }

    /**
     * 关闭寄售，自动下架市场所有专卖中的商品
     * @param id 藏品，装备，宝盒id
     * @param type 1藏品 2装备 3宝盒
     */
    public void offShelfAuto(Long id, Integer type) {
        //查询市场所有专卖中的商品id
        LambdaQueryWrapper<Market> wp = new LambdaQueryWrapper<>();
        if (type.equals(MarketTypeEnum.NORMAL.getValue()) || type.equals(MarketTypeEnum.EQUIPMENT.getValue())) {
            wp.eq(Market::getNftId, id);
            wp.in(Market::getType, MarketTypeEnum.NORMAL, MarketTypeEnum.EQUIPMENT);
        } else if (type.equals(MarketTypeEnum.BOX.getValue())) {
            wp.eq(Market::getBoxId, id);
        } else {
            throw new ServiceException("暂不支持的类型");
        }
        wp.eq(Market::getStatus, MarketStatusEnum.TRADING);

        List<Market> markets = this.list(wp);
        if (CollUtil.isNotEmpty(markets)) {
            markets.forEach(market -> {
                try {
                    cancelSaleable(market.getUserId(), market.getId());
                } catch (Exception e) {
                    log.info(e.getMessage());
                }
            });
        }
    }

    /**
     * 开启寄售同时显示外显价格，市场新增售出的商品
     * @param id 藏品，装备，宝盒id
     * @param type 1藏品 2装备 3宝盒
     */
    public void addMarketVirtual(Long id, BigDecimal displayMaxPrice, Integer type, String title) {
        Market market = new Market();
        market.setId(IdUtil.getSnowflakeNextId());
        market.setNftId(id);
        market.setNftNumberId(1L);
        market.setNumberValueStr("1");
        market.setStatus(MarketStatusEnum.SOLD_OUT);
        market.setImg("");
        market.setSourceId(1L);
        market.setPrice(displayMaxPrice);
        market.setSales(1);
        market.setStock(0);
        market.setLockStock(0);
        market.setTotalStock(1);
        market.setTitle(title);
        market.setUserId(1L);
        if (Objects.equals(type, MarketTypeEnum.NORMAL.getValue())) {
            market.setType(MarketTypeEnum.NORMAL);
        } else if (type.equals(MarketTypeEnum.EQUIPMENT.getValue())) {
            market.setType(MarketTypeEnum.EQUIPMENT);
        }
        boolean save = save(market);
        if (!save) {
            throw new ServiceException("配置外显价格失败！");
        }
    }

    /**
     * 关闭寄售，同时删除该nft已售的商品防止老数据影响最高价格
     * @param id 藏品，装备，宝盒id
     * @param type 1藏品 2装备 3宝盒
     */
    public void delMarketByNftId(Long id, Integer type) {
        //删除市场已售的商品
        LambdaQueryWrapper<Market> wp = new LambdaQueryWrapper<>();
        if (type.equals(MarketTypeEnum.NORMAL.getValue()) || type.equals(MarketTypeEnum.EQUIPMENT.getValue())) {
            wp.eq(Market::getNftId, id);
            wp.in(Market::getType, MarketTypeEnum.NORMAL, MarketTypeEnum.EQUIPMENT);
        } else if (type.equals(MarketTypeEnum.BOX.getValue())) {
            wp.eq(Market::getBoxId, id);
        } else {
            throw new ServiceException("暂不支持的类型");
        }
        wp.eq(Market::getStatus, MarketStatusEnum.SOLD_OUT);
        this.remove(wp);
    }
}
